/**
 * 
 */
package appcomp.graphtools;

import appcomp.entities.*;
import java.util.ArrayList;
import java.util.List;

/**
 * @author riche
 *
 */
public class LagniappeGraph {
	
	public enum CompressionPolicy {
		Strict, Greedy;
	}
	
	private ArrayList<GraphNodeWrapper> headList;
	private CompressionPolicy policy;
	
	/**
	 * @param policy
	 */
	public LagniappeGraph(CompressionPolicy policy) {
		this.policy = policy;
		this.setup();
	}

	public LagniappeGraph() {
		this.policy = CompressionPolicy.Strict;
		this.setup();
	}
	
	private void setup() {
		headList = new ArrayList<GraphNodeWrapper>();
	}

	public List<GraphNodeWrapper> getList() {return headList;}
	
	public void addHead(GraphNodeWrapper node) {headList.add(node);}

	/**
	 * @return the policy
	 */
	public CompressionPolicy getPolicy() {
		return policy;
	}

	/**
	 * @param policy the policy to set
	 */
	public void setPolicy(CompressionPolicy policy) {
		this.policy = policy;
	} 
	
	public ArrayList<Operator> compressGraph() {
		ArrayList<Operator> collector = new ArrayList<Operator>();
		for(GraphNodeWrapper n : headList) {
			Composite temp = new Composite();
			
			compressNode(n, temp, collector);
		}
		return collector;
	}
	
	private void compressNode(GraphNodeWrapper n, Composite cmp, List<Operator> collector) {
		for(GraphNodeWrapper c : n.getChildren()) {
			if(this.policy == CompressionPolicy.Strict) {
				if(c.getFlowSignature().equals(n.getFlowSignature()) && !c.isInComposite()) {
					cmp.addToOpList(c.getCoreOp());
					c.joinedComposite();
					compressNode(c, cmp, collector);
					if(!n.isInComposite()) {
						cmp.addToOpList(n.getCoreOp());
						n.joinedComposite();
					}
				}
				break;
			}
			else if(this.policy == CompressionPolicy.Greedy) {
			    // check to see if either the flow sig matches or it has no flow sig
				if((c.getFlowSignature().equals(n.getFlowSignature()) || c.getFlowSignature().equals("NULL")) && !c.isInComposite()) {
				    // add to the composite
					cmp.addToOpList(c.getCoreOp());
					// mark the operator is in a composite
					c.joinedComposite();
					// if it has a null flow sig, then give it the flow sig of the composite (not sure why I'm doing this)
					if(c.getFlowSignature().equals("NULL")) {
						c.setFlowSignature(n.getFlowSignature());
					}
					// keep going on child of current child
					compressNode(c, cmp, collector);
					// put the current node into the composite if it isn't already
					// this must be a start case thing
					if(!n.isInComposite()) {
						cmp.addToOpList(n.getCoreOp());
						n.joinedComposite();
					}
				}
			}
		}
		if(n.isInComposite() && !collector.contains(cmp)) {
			collector.add(cmp);
		}
		else if (!n.isInComposite() && !collector.contains(n.getCoreOp())) {
			collector.add(n.getCoreOp());
		}
		for(GraphNodeWrapper c : n.getChildren()) {
			if(!c.isInComposite()) {
				compressNode(c, new Composite(), collector);
			}
		}
		if(!n.getFlowSignature().equals("NULL")) {
		    cmp.setStateful(true);
		    State state = null;
            try {
                state = new State(null, null, null, n.getCoreOp().getState().getFlowSig(), null, null);
            } catch (Exception e) {
            }
		    cmp.setState(state);
		}
	}
	
	public void formGraph(List<Operator> opList, List<Connector> conList) {
		Type masterType = null;
		masterType = this.getHead(opList, conList);
		for(GraphNodeWrapper n : headList) {
			findSubGraph(n, masterType, conList);
		}
		this.printGraph();
	}
	
	private Type getHead(List<Operator> opList, List<Connector> conList) {
		Type type = null;
		for (Operator o : opList) {
			if(o.doesGenerate()) {
				for (Port p : o.getPortList()) {
					if(p.getDirection() == Port.Direction.OUT) {
						type = p.getType();
						for(Connector c : conList) {
							if(c.isFrom(o.getName(), p.getName())) {
								this.addHead(new GraphNodeWrapper(c.getOut()));
								conList.remove(c);
								break;
							}
						}
						
					}
				}
				
			}
		}
		return type;
	}
	
	private void findSubGraph(GraphNodeWrapper n, Type t, List<Connector> conList) {
		ArrayList<Port> goodPortList = new ArrayList<Port>();
		for(Port p : n.getCoreOp().getPortList()) {
			if(p.getDirection() == Port.Direction.OUT && p.getType().equals(t)) {
				goodPortList.add(p);
			}
			else if(p.getDirection() == Port.Direction.OUT) {
				ArrayList<Connector> deadConns = new ArrayList<Connector>();
				for(Connector c : conList) {
					if(c.isFrom(n.getCoreOp().getName(), p.getName())) {
						deadConns.add(c);
					}
				}
				for(Connector c : deadConns) {
					conList.remove(c);
				}
			}
		}
		if(!conList.isEmpty()) {
			for(Port p : goodPortList) {
				ArrayList<Connector> deadConns = new ArrayList<Connector>();
				for(Connector c : conList) {
					if(c.isFrom(n.getCoreOp().getName(), p.getName())) {
						if(!c.getOut().doesGenerate()) {
							n.addChild(new GraphNodeWrapper(c.getOut()));
							deadConns.add(c);
						}
					}
				}
				for(Connector c : deadConns) {
					conList.remove(c);
				}
			}
		}
		if(n.hasChildren() && !conList.isEmpty()) {
			for(GraphNodeWrapper child : n.getChildren()) {
				findSubGraph(child, t, conList);
			}
		}
	}
	
	private void printGraph() {
	    System.out.println("Printing graph nodes:");
		for(GraphNodeWrapper h : headList) {
			h.printNode();
		}
	}
	
	public boolean isEmpty() {
		return this.headList.isEmpty();
	}
	
	public ArrayList<Connector> consumeConnectors(List<Operator> opList, List<Connector> conList) {
		ArrayList<Connector> newConList = new ArrayList<Connector>();
		for(Operator op : opList) {
			op.consumeConnectors(conList, newConList);
		}
		return newConList;
	}

}
